﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Ewk.Runtime.Serialization.CharacterSeparated
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public sealed class CharacterSeparatedDatafieldAttribute : DatafieldAttribute
    {
        static readonly Dictionary<Type, DatafieldSerializer<CharacterSeparatedDatafieldAttribute>> _serializers = new Dictionary<Type, DatafieldSerializer<CharacterSeparatedDatafieldAttribute>>();

        static CharacterSeparatedDatafieldAttribute()
        {
            DatafieldSerializer<CharacterSeparatedDatafieldAttribute> datafieldSerializer = null;

            datafieldSerializer = new CharacterSeparatedInt32Serializer();
            _serializers.Add(datafieldSerializer.DatafieldType, datafieldSerializer);

            datafieldSerializer = new CharacterSeparatedDateTimeSerializer();
            _serializers.Add(datafieldSerializer.DatafieldType, datafieldSerializer);
        }

        public CharacterSeparatedDatafieldAttribute(int serialNumber)
        {
            if (serialNumber <= 0) throw new ArgumentException(ExceptionMessages.InvalidSerialnumberValue, "serialNumber");

            SerialNumber = serialNumber;
        }
        
        /// <summary>
        /// Gets a number that defines the order of fields within a datarecord.
        /// </summary>
        public int SerialNumber { get; private set; }

        /// <summary>
        /// Gets of sets a value that is used as character to separate fields in the the string representation of the datarecord.
        /// </summary>
        internal char SeparationCharacter { get; set; }

        public override string Serialize<T>(T datarecord)
        {
            var value = PropertyInfo.GetValue(datarecord, null);
            var fieldSerializer = GetDatafieldSerializer();

            if (PropertyInfo.PropertyType.IsArray)
            {
                return SerializeArray(value, fieldSerializer);
            }

            return fieldSerializer.Serialize(value, this);
        }

        string SerializeArray(object value, DatafieldSerializer<CharacterSeparatedDatafieldAttribute> fieldSerializer)
        {
            var resultBuilder = new StringBuilder();
            var elements = (Array)value;

            foreach (var element in elements)
            {
                var elementResult = fieldSerializer.Serialize(element, this);
                resultBuilder.Append(elementResult);
                resultBuilder.Append(SeparationCharacter);
            }

            var result = resultBuilder.ToString();
            return result.TrimEnd(SeparationCharacter);
        }

        public override object Deserialize(string datafield)
        {
            if (string.IsNullOrEmpty(datafield) && Mandatory)
            {
                OnDatafieldEmpty(this, ExceptionMessages.DatafieldEmpty);
            }

            var fieldSerializer = GetDatafieldSerializer();

            if (PropertyInfo.PropertyType.IsArray)
            {
                return DeserializeArray(datafield, fieldSerializer);
            }

            return fieldSerializer.Deserialize(datafield, this);
        }

        Array DeserializeArray(string value, DatafieldSerializer<CharacterSeparatedDatafieldAttribute> fieldSerializer)
        {
            var array = Array.CreateInstance(ElementType, Occurrence);

            var valueElements = value.Split(SeparationCharacter);
            for (var i = 0; i < Occurrence; i++)
            {
                var result = fieldSerializer.Deserialize(valueElements[i], this);
                array.SetValue(result, i);
            }

            return array;
        }

        DatafieldSerializer<CharacterSeparatedDatafieldAttribute> GetDatafieldSerializer()
        {
            if (SerializerType == null)
            {
                return _serializers[ElementType];
            }

            return (DatafieldSerializer<CharacterSeparatedDatafieldAttribute>)SerializerType.Assembly.CreateInstance(SerializerType.FullName);
        }
    }
}
